
package com.hlkj.pay.app.order.impl;
/*
 * Hlpay-Plus aggregate payment system.
 * Copyright (c) 2024-2025 Hlpay Team Copyright has the right of final interpretation.
 */

import com.hlkj.framework.common.util.collection.CollectionUtils;
import com.hlkj.framework.common.util.json.JsonUtils;
import com.hlkj.pay.app.order.IMerchantPaymentStatisticsAppService;
import com.hlkj.pay.dto.payment.MerchantPaymentDetailDto;
import com.hlkj.pay.enums.PayOrderEnums;
import com.hlkj.pay.infrastructure.model.payment.MerchantPaymentStatisticsDO;
import com.hlkj.pay.service.payment.IMerchantPaymentStatisticsService;
import com.hlkj.pay.util.MerchantUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author HlpayTeam
 * @date 2024/10/24 12:41
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MerchantPaymentStatisticsAppServiceImpl implements IMerchantPaymentStatisticsAppService {

    private final IMerchantPaymentStatisticsService paymentStatisticsService;

    @Override
    public void processStatisticsMessage(List<MerchantPaymentDetailDto> statisticsList) {
        try {
            log.info("收到代付统计消息处理 size:{}", statisticsList.size());
            Map<String, List<MerchantPaymentDetailDto>> snOrderMap = statisticsList.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getSn()));
            // 支付方式只按照大类统计
            Map<String, List<MerchantPaymentDetailDto>> payWayOrderMap = statisticsList.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getPaymentType().toString()));
            Map<String, List<MerchantPaymentDetailDto>> appIdOrderMap = statisticsList.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getAppId()));
            Map<String, List<MerchantPaymentDetailDto>> mchChannelOrderMap = statisticsList.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getMchChannelCode()));
            // 商户维度
            snOrderMap.forEach((key, value) -> {
                processTimeStatistics(key, PayOrderEnums.ORDER_STATISTICS_TYPE.SN, key, value);
                value.stream().forEach(v -> {
                    log.info("商户代付维度统计数据 sn:{},data:{}", v.getSn(), JsonUtils.toJsonStringNotNull(v));
                });
            });
            // 支付方式维度
            payWayOrderMap.forEach((key, value) -> {
                Map<String, List<MerchantPaymentDetailDto>> snMap = value.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getSn()));
                snMap.forEach((sn, valueList) -> {
                    processTimeStatistics(sn, PayOrderEnums.ORDER_STATISTICS_TYPE.PAY_TYPE, key.toString(), valueList);
                });
            });
            // app维度
            appIdOrderMap.forEach((key, value) -> {
                Map<String, List<MerchantPaymentDetailDto>> snMap = value.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getSn()));
                snMap.forEach((sn, valueList) -> {
                    processTimeStatistics(sn, PayOrderEnums.ORDER_STATISTICS_TYPE.APPID, key, valueList);
                });
            });
            // mchChannelCode维度
            mchChannelOrderMap.forEach((key, value) -> {
                Map<String, List<MerchantPaymentDetailDto>> snMap = value.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getSn()));
                snMap.forEach((sn, valueList) -> {
                    processTimeStatistics(sn, PayOrderEnums.ORDER_STATISTICS_TYPE.MCH_CHANNEL_CODE, key, valueList);
                });
            });
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 不同维度的统计
     *
     * @param statisticsType
     * @param relationId
     * @param statisticsDtos
     */
    private void processTimeStatistics(String sn, PayOrderEnums.ORDER_STATISTICS_TYPE statisticsType, String relationId, List<MerchantPaymentDetailDto> statisticsDtos) {
        // key 当前时间段
        Map<Long, List<MerchantPaymentDetailDto>> timeRangeMap = statisticsDtos.stream().collect(Collectors.groupingBy(adStatisticsDto -> coverTime(adStatisticsDto.getCreateTime())));
        Set<Long> timeKeys = timeRangeMap.keySet();

        List<MerchantPaymentStatisticsDO> statisticsDbList = paymentStatisticsService.queryList(sn, statisticsType.getCode(), relationId, timeKeys);
        if (CollectionUtils.isEmpty(statisticsDbList)) {
            statisticsDbList = new ArrayList<>();
        }
        Map<Long, MerchantPaymentStatisticsDO> timeKeyDbMap = statisticsDbList.stream()
                .collect(Collectors.toMap(adStatisticsDO -> adStatisticsDO.getTime(), Function.identity(), (t1, t2) -> t1));
        Set<Long> timeKeyDbs = timeKeyDbMap.keySet();
        timeRangeMap.forEach((key, value) -> {
            log.info("代付统计维度 sn:{},type:{},key:{}", sn, statisticsType.getMsg(), key);
            // 存在 进行更新
            if (timeKeyDbs.contains(key)) {
                MerchantPaymentStatisticsDO adOrderStatisticsDO = calcStatisticsDim(value);
                MerchantPaymentStatisticsDO adStatistics = timeKeyDbMap.get(key);
                adOrderStatisticsDO.setId(adStatistics.getId());
                paymentStatisticsService.updatePaymentDetail(adOrderStatisticsDO);
            }
            else {
                log.info("新增代付统计维度 sn:{},type:{},key:{}", sn, statisticsType.getMsg(), key);
                MerchantPaymentStatisticsDO statisticsDim = calcStatisticsDim(value);
                statisticsDim.setTime(key);
                statisticsDim.setType(statisticsType.getCode());
                statisticsDim.setRelationId(relationId);
                statisticsDim.setSn(sn);
                try {
                    paymentStatisticsService.addPaymentDetail(statisticsDim);
                }
                catch (Exception e) {
                    log.error("新增代付统计维度 fail type:{},relationId:{}, message:{}", statisticsType.getMsg(), relationId, e.getMessage(), e);
                    // type relationId 与time做了唯一索引 新增失败进行更新
                    MerchantPaymentStatisticsDO orderStatisticsDO = paymentStatisticsService.queryOne(sn, statisticsType.getCode(), relationId, key);
                    if (orderStatisticsDO == null) {
                        paymentStatisticsService.addPaymentDetail(statisticsDim);
                    }
                    else {
                        statisticsDim.setId(orderStatisticsDO.getId());
                        paymentStatisticsService.updatePaymentDetail(statisticsDim);
                    }
                }
            }
        });
    }

    public static Long coverTime(Long originalTimestamp) {
        // 将时间戳转换为 Instant
        Instant instant = Instant.ofEpochMilli(originalTimestamp);

        // 使用东八区时区计算当天零点
        ZoneId zoneId = ZoneId.of("Asia/Shanghai");
        LocalDateTime localDateTime = instant.atZone(zoneId).toLocalDate().atStartOfDay();

        // 转换回时间戳
        return localDateTime.atZone(zoneId).toInstant().toEpochMilli();
    }

    MerchantPaymentStatisticsDO calcStatisticsDim(List<MerchantPaymentDetailDto> adStatisticsList) {
        List<MerchantPaymentDetailDto> payOrderList = new ArrayList<>();
        List<MerchantPaymentDetailDto> submitOrderList = new ArrayList<>();
        List<MerchantPaymentDetailDto> refundOrderList = new ArrayList<>();
        adStatisticsList.forEach(obj -> {
            // 0 待审核 1 审核通过 2 执行中 3 执行完成 4 执行失败 5 审核拒绝 6 交易退票
            if (obj.getStatus() == 3) {
                payOrderList.add(obj);
            } else if (obj.getStatus() == 6) {
                refundOrderList.add(obj);
            } else {
                submitOrderList.add(obj);
            }
        });

        Integer payOrderCount = 0;
        Integer submitCount = 0;
        Integer refundCount = 0;
        Double payOrderAmount = new Double(0);
        Double feeAmount = new Double(0);
        Double channelFeeAmount = new Double(0);
        Double submitAmount = new Double(0);
        Double refundAmount = new Double(0);
        if (!CollectionUtils.isEmpty(payOrderList)) {
            payOrderCount = payOrderList.size();
            payOrderAmount = payOrderList.stream().filter(statisticsDto -> statisticsDto.getPayAmount() != null)
                    .mapToDouble(statisticsDto -> statisticsDto.getPayAmount().doubleValue()).sum();
            feeAmount = payOrderList.stream().filter(statisticsDto -> statisticsDto.getFeeAmount() != null)
                    .mapToDouble(statisticsDto -> statisticsDto.getFeeAmount().doubleValue()).sum();
            channelFeeAmount = payOrderList.stream().filter(statisticsDto -> statisticsDto.getThirdDealAmount() != null)
                    .mapToDouble(statisticsDto -> statisticsDto.getThirdDealAmount().doubleValue()).sum();
        }
        if (!CollectionUtils.isEmpty(submitOrderList)) {
            submitCount = submitOrderList.size();
            submitAmount = submitOrderList.stream().mapToDouble(adStatisticsDto -> adStatisticsDto.getPayAmount().doubleValue()).sum();
        }
        if (!CollectionUtils.isEmpty(refundOrderList)) {
            refundCount = refundOrderList.size();
            refundAmount = refundOrderList.stream().mapToDouble(adStatisticsDto -> adStatisticsDto.getPayAmount().doubleValue()).sum();
        }
        MerchantPaymentStatisticsDO orderStatisticsDO = new MerchantPaymentStatisticsDO();
        orderStatisticsDO.setSubmitOrderCount(submitCount);
        orderStatisticsDO.setSubmitOrderAmount(MerchantUtils.doubleToBigDecimal(submitAmount));
        orderStatisticsDO.setPayOrderCount(payOrderCount);
        orderStatisticsDO.setPayOrderAmount(MerchantUtils.doubleToBigDecimal(payOrderAmount));
        orderStatisticsDO.setFeeAmount(MerchantUtils.doubleToBigDecimal(feeAmount));
        orderStatisticsDO.setChannelFeeAmount(MerchantUtils.doubleToBigDecimal(channelFeeAmount));
        orderStatisticsDO.setRefundCount(refundCount);
        orderStatisticsDO.setRefundAmount(MerchantUtils.doubleToBigDecimal(refundAmount));
        return orderStatisticsDO;
    }

    public static void main(String[] args) {
        System.out.println(coverTime(System.currentTimeMillis()));
    }
}
